package com.wuhao.code.check.highlight

import com.aegis.mybatis.xmlless.annotations.XmllessMethod
import com.aegis.mybatis.xmlless.model.MethodInfo
import com.aegis.mybatis.xmlless.model.base.IExpressionToken
import com.aegis.mybatis.xmlless.model.component.ExpressionGroup
import com.aegis.mybatis.xmlless.model.component.StringExpression
import com.aegis.mybatis.xmlless.model.exception.BuildSQLException
import com.aegis.mybatis.xmlless.resolver.ColumnsResolver
import com.aegis.mybatis.xmlless.resolver.CriteriaResolver
import com.aegis.mybatis.xmlless.resolver.ExpressionResolver
import com.fasterxml.jackson.databind.util.LRUMap
import com.intellij.codeInsight.daemon.impl.HighlightInfoType
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.lang.annotation.AnnotationHolder
import com.intellij.lang.annotation.Annotator
import com.intellij.openapi.editor.DefaultLanguageHighlighterColors
import com.intellij.openapi.editor.colors.TextAttributesKey
import com.intellij.openapi.editor.markup.EffectType
import com.intellij.openapi.editor.markup.TextAttributes
import com.intellij.openapi.util.TextRange
import com.intellij.psi.*
import com.wuhao.code.check.inspection.visitor.BaseXmllessGenerator
import com.wuhao.code.check.inspection.visitor.getXmllessMapperModelType
import com.wuhao.code.check.model.KtClassWrapper
import com.wuhao.code.check.model.KtMethodWrapper
import com.wuhao.code.check.model.PsiClassWrapper
import com.wuhao.code.check.model.PsiMethodWrapper
import org.jetbrains.kotlin.idea.editor.fixers.range
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.parents
import java.awt.Color
import java.awt.Font


/**
 * Created by 吴昊 on 2023/12/11.
 *
 * @author 吴昊
 * @version 1.0
 * @date 2023/12/11 12:51
 * @since v0.0.0
 */
class XmllessMethodAnnotationHighlighter : Annotator {

  override fun annotate(element: PsiElement, holder: AnnotationHolder) {
    if (element is PsiAnnotation) {
      processPsiAnnotation(element, holder)
    }
    if (element is KtAnnotationEntry) {
      processAnnotation(element, holder)
    }
  }

  private fun processAnnotation(element: KtAnnotationEntry, holder: AnnotationHolder) {
    if (element.typeReference?.getTypeText() != "XmllessMethod") {
      return
    }
    val fn = element.parents.first { it is KtNamedFunction } as KtNamedFunction
    val modelType = getXmllessMapperModelType(fn) as KtClass
    val (_, methodInfo) = BaseXmllessGenerator.createMethodInfo(
        modelType.fqName!!.toString(),
        KtClassWrapper(modelType)
    ) { tableInfo ->
      KtMethodWrapper(fn, KtClassWrapper(modelType), tableInfo)
    }
    element.valueArguments.forEach {
      process(it, holder, methodInfo)
    }
  }

  private fun processPsiAnnotation(element: PsiAnnotation, holder: AnnotationHolder) {
    if (element.qualifiedName != XmllessMethod::class.java.name) {
      return
    }
    val fn = element.parents.first { it is PsiMethod } as PsiMethod
    val modelType = getXmllessMapperModelType(fn) as PsiClass
    val (_, methodInfo) = BaseXmllessGenerator.createMethodInfo(
        modelType.qualifiedName!!,
        PsiClassWrapper(modelType)
    ) { tableInfo ->
      PsiMethodWrapper(fn, PsiClassWrapper(modelType), tableInfo)
    }
    element.parameterList.attributes.filter {
      it.name in Util.FIELDS_TO_RESOLVE
    }.forEach {
      val v = it.value
      if (v is PsiArrayInitializerMemberValue) {
        v.initializers.forEach { stringValueExpression ->
          processStringElement(
              stringValueExpression.text.drop(1).dropLast(1),
              holder, stringValueExpression, 1, methodInfo
          )
        }
      } else if (v is PsiLiteralExpression) {
        processStringElement(v.text.drop(1).dropLast(1), holder, v, 1, methodInfo)
      }
    }
  }

  private fun process(
      valueArgument: ValueArgument,
      holder: AnnotationHolder,
      methodInfo: MethodInfo
  ) {
    if (!valueArgument.isNamed() || valueArgument.getArgumentName()?.asName?.asString() !in Util.FIELDS_TO_RESOLVE) {
      return
    }
    val argExp = valueArgument.getArgumentExpression()
    if (argExp !is KtCollectionLiteralExpression) {
      return
    }
    argExp.innerExpressions.forEach { exp ->
      processInner(exp, holder, methodInfo)
    }
  }

  private fun processInner(
      exp: KtExpression?,
      holder: AnnotationHolder,
      methodInfo: MethodInfo
  ) {
    if (exp !is KtStringTemplateExpression) {
      return
    }
    exp.entries.forEach { entry ->
      val text = entry.text
      processStringElement(text, holder, entry, 0, methodInfo)
    }
  }

  private fun processStringElement(
      text: String,
      holder: AnnotationHolder,
      entry: PsiElement,
      fixOffset: Int = 0,
      methodInfo: MethodInfo
  ) {
    val nodes = Util.parseExpression(text)
    val conditionExpression = CriteriaResolver.parseExpression(text)
    if (conditionExpression is ExpressionGroup) {
      highlightGroup(conditionExpression, holder, entry, fixOffset, methodInfo)
    } else if (conditionExpression is StringExpression) {
      highlightTokens(conditionExpression.tokens, holder, entry, fixOffset, methodInfo)
    }
    nodes.forEach {
      val type = when (it::class.java.simpleName) {
        "LeftBrace",
        "RightBrace" -> DefaultLanguageHighlighterColors.BRACES

        "Connector"  -> DefaultLanguageHighlighterColors.KEYWORD
        else         -> null
      }
      if (type != null && it.toSql().lowercase() !in Util.SQL_KEYWORDS) {
        highlight(it.start + fixOffset, it.end + 1 + fixOffset, holder, entry, type)
      }
    }
  }

  private fun highlightTokens(
      tokens: List<IExpressionToken>,
      holder: AnnotationHolder,
      entry: PsiElement,
      fixOffset: Int,
      methodInfo: MethodInfo
  ) {
    var beforeOperator = true
    tokens.forEach { token ->
      if (token::class.java.simpleName == "Operator") {
        beforeOperator = false
      }
      val type = when (token::class.java.simpleName) {
        "Token"        -> {
          if (token.toSql().lowercase() in Util.SQL_KEYWORDS) {
            DefaultLanguageHighlighterColors.KEYWORD
          } else if (beforeOperator) {
            try {
              val res = ColumnsResolver.resolveColumnByPropertyName(token.toSql(), methodInfo)
              if (res.size == 1) {
                DefaultLanguageHighlighterColors.INSTANCE_FIELD
              } else {
                Util.ERROR_KEY
              }
            } catch (e: BuildSQLException) {
              Util.ERROR_KEY
            }
          } else {
            val p = methodInfo.findOptionalParam(token.toSql())
            if (p != null) {
              DefaultLanguageHighlighterColors.STATIC_FIELD
            } else {
              Util.ERROR_KEY
            }
          }
        }

        "Operator"     -> DefaultLanguageHighlighterColors.KEYWORD
        "FunctionName" -> DefaultLanguageHighlighterColors.FUNCTION_CALL
        "NumberValue"  -> DefaultLanguageHighlighterColors.NUMBER
        "BoolValue"    -> DefaultLanguageHighlighterColors.CONSTANT
        else           -> null
      }
      if (type != null) {
        highlight(token.start + fixOffset, token.end + 1 + fixOffset, holder, entry, type)
      }
    }
  }

  private fun highlightGroup(
      exp: ExpressionGroup,
      holder: AnnotationHolder,
      entry: PsiElement,
      fixOffset: Int,
      methodInfo: MethodInfo
  ) {
    exp.expressions.forEach {
      if (it is ExpressionGroup) {
        highlightGroup(it, holder, entry, fixOffset, methodInfo)
      } else if (it is StringExpression) {
        highlightTokens(it.tokens, holder, entry, fixOffset, methodInfo)
      }
    }
  }

  private fun highlight(
      start: Int,
      end: Int,
      holder: AnnotationHolder,
      entry: PsiElement,
      key: TextAttributesKey
  ) {
    val builder = holder.newAnnotation(
        HighlightInfoType.HIGHLIGHTED_REFERENCE_SEVERITY, ""
    )
    builder.textAttributes(key)
    builder.range(TextRange(entry.range.startOffset + start, entry.range.startOffset + end))
    builder.create()
  }

  object Util {

    val FIELDS_TO_RESOLVE = listOf("conditions", "includes", "sortBy", "groupBy")
    private val CACHE: LRUMap<String, List<IExpressionToken>> = LRUMap(0, 1000)
    val ERROR_KEY = TextAttributesKey.createTextAttributesKey(
        "ERROR_KEY",
        TextAttributes(Color.RED, null, null, EffectType.BOXED, Font.PLAIN)
    )
    val SQL_KEYWORDS = listOf(
        "order", "by", "desc", "asc", "group", "having", "distinct"
    )

    fun parseExpression(text: String): List<IExpressionToken> {
      if (CACHE[text] != null) {
        return CACHE[text]
      }
      val tokens = ExpressionResolver.parseExpression(text).getNodes()
          .filter { it::class.java.simpleName != "Whitespace" }
      CACHE.put(text, tokens)
      return tokens
    }

  }

}
